package main;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.concurrent.ArrayBlockingQueue;

/**
 * Handles a socket received from client. Closes the socket after handling it.
 * 
 */
public class ServerHandlerThread extends Thread {
  private final ArrayBlockingQueue<Socket> socketsQueue;
  private Socket clientSocket;
  private static final int timeout = 10000; // in milliseconds
  private static final int MAX_DATA_FILE_SIZE = 1024;
  
  ServerHandlerThread(ArrayBlockingQueue<Socket> socketsQueue) {
    this.socketsQueue = socketsQueue;
  }
  
  private void handleHello(InputStream is) throws IOException {
    Socket sock = null;
    OutputStream os = null;
    InputStream isForFile = null; // for getting the file
    // get hello message parameters
    long lastModified = parseNextNumericParameter(is);
    int clientListeningPort = (int) parseNextNumericParameter(is);
    // if the time stamp is newer than current time stamp,
    // sent a get message to the client and wait for the file
    try {
      if (lastModified > Sync_app.getLastModified()) {
        InetAddress ipAddress = clientSocket.getInetAddress();
        sock = new Socket(ipAddress, clientListeningPort);
        sock.setSoTimeout(timeout);
        // send the GET message
        os = sock.getOutputStream();
        os.write(createGetMsg());
        os.flush();
        // wait for the file
        isForFile = sock.getInputStream();
        byte[] dataFile = new byte[MAX_DATA_FILE_SIZE];
        int bytesRead = isForFile.read(dataFile, 0, dataFile.length);
        int current = bytesRead;
        while (bytesRead > -1) {
          bytesRead = isForFile.read(dataFile, current, dataFile.length - current);
          if (bytesRead >= 0)
            current += bytesRead;
        }
        WriteToFile(dataFile, lastModified, current);
      }
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        if (isForFile != null)
          isForFile.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
      try {
        if (os != null)
          os.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
      try {
        if (sock != null)
          sock.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
  
  private byte[] createGetMsg() {
    String s = Sync_app.GET_MSG_STR;
    return s.getBytes();
  }
  
  private void handleGet(Socket sock) {
    int fileLength = (int) Sync_app.myFile.length();
    byte[] mybytearray = new byte[fileLength];
    FileInputStream fis;
    OutputStream os = null;
    try {
      fis = new FileInputStream(Sync_app.myFile);
      BufferedInputStream bis = new BufferedInputStream(fis);
      bis.read(mybytearray, 0, fileLength);
      bis.close();
      os = sock.getOutputStream();
      os.write(mybytearray, 0, fileLength);
      os.flush();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        if (os != null)
          os.close();
      } catch (IOException e1) {
        e1.printStackTrace();
      }
    }
  }
  
  /*
   * retrieves next parameter in the input stream message
   */
  private long parseNextNumericParameter(InputStream is) throws IOException {
    long parameter = 0;
    int byteRead = -1;
    byteRead = is.read();
    while (byteRead > -1 && byteRead != ' ') {
      parameter *= 10;
      parameter += Character.getNumericValue((char) byteRead);
      byteRead = is.read();
    }
    return parameter;
  }
  
  @Override public void run() {
    InputStream is = null;
    int byteRead = -1;
    while (true)
      try {
        clientSocket = socketsQueue.take();
        is = clientSocket.getInputStream();
        byteRead = is.read();
        // check EOF for empty sockets
        if (byteRead < 0)
          continue;
        if (byteRead == Sync_app.HELLO_MSG_VAL) {
          byteRead = is.read();
          // should be space
          if ((char) byteRead != ' ')
            continue;
          handleHello(is);
        } else if (byteRead == Sync_app.GET_MSG_VAL)
          handleGet(clientSocket);
      } catch (InterruptedException | IOException e) {
        e.printStackTrace();
      } finally {
        try {
          is.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
        try {
          clientSocket.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
  }
  
  private void WriteToFile(byte[] data, long modifiedTime, int dataLen) {
    try {
      if (!Sync_app.myFile.exists())
        Sync_app.myFile.createNewFile();
      FileOutputStream fos = new FileOutputStream(Sync_app.myFile.getAbsoluteFile());
      BufferedOutputStream bos = new BufferedOutputStream(fos);
      bos.write(data, 0, dataLen);
      bos.flush();
      bos.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
    Sync_app.setLastModifiedAfterWrite(modifiedTime);
  }
  
  public static String parseTime(long time) {
    SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
    return sdf.format(time);
  }
}